package com.neteast.androidclient.newscenter.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import android.util.Log;
import android.widget.Toast;
import com.neteast.androidclient.newscenter.Global;
import com.neteast.androidclient.newscenter.NewsCenter;
import com.neteast.androidclient.newscenter.db.InfoDao;
import com.neteast.androidclient.newscenter.domain.BootStrapError;
import com.neteast.androidclient.newscenter.domain.BootStrapRequest;
import com.neteast.androidclient.newscenter.domain.BootStrapResponse;
import com.neteast.androidclient.newscenter.domain.InfoReceive;
import com.neteast.androidclient.newscenter.domain.InfoReceiveResponse;
import com.neteast.androidclient.newscenter.domain.Information;
import com.neteast.androidclient.newscenter.domain.KeepLive;
import com.neteast.androidclient.newscenter.domain.LoginError;
import com.neteast.androidclient.newscenter.domain.LoginResponse;
import com.neteast.androidclient.newscenter.domain.Logout;
import com.neteast.androidclient.newscenter.service.NetService;
import com.neteast.androidclient.newscenter.service.WidgetService;
import com.neteast.androidclient.newscenter.util.ConfigUtil;
import com.neteast.androidclient.newscenter.util.DeBugUtil;
import com.neteast.androidclient.newscenter.util.InfoUtil;
import com.neteast.androidclient.newscenter.util.JsonUtil;
import com.neteast.androidclient.newscenter.util.LoginUtil;

public class NewsCenterClient implements IOHandler {
	
	private NetService netService;
	
	private DatagramChannel client;
	private Selector selector;
	public static HashSet<Long> userOptInfos=new HashSet<Long>();
	
	HashMap<Integer, String> broadcastBuffer=new HashMap<Integer, String>();
	HashMap<Integer, String> unicastbuffer=new HashMap<Integer, String>();
	
	private long broadcastId;
	private long unicastId;
	/** 超时时间*/
	private long timeoutMillis;
	/** 是否是第一次返回登陆响应报文，用来判断是否开启心跳*/
	private boolean first=true;
	/** 是否监听*/
	public boolean isListening=false;
	/** 是否超时*/
	private boolean timeout=false;
	/** 是否保活*/
	private boolean keeplive=false;
	/** 保活次数*/
	private int keepAliveCount=1;
	
	private int lastUserId;
	private AtomicInteger msgId;
	/**执行bootstrap请求的线程*/
	private Thread bootStrapRequesThread;
	/**执行登陆操作的线程*/
	private Thread loginThread;
	/**执行保活操作的线程*/
	private Thread keepAliveThread;
	/**执行监听操作的线程*/
	private Thread listeningThread;
	/**执行保存操作的线程*/
	private Thread saveInfoThread;
	
	public NewsCenterClient(NetService service) {
		netService = service;
		lastUserId=LoginUtil.getUserId(service);
		msgId=new AtomicInteger(0);
	}
	
	
	public void initClient() throws IOException {
		client = DatagramChannel.open();
		client.configureBlocking(false);
		selector = Selector.open();
		client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
		Log.i("test", "初始化通道");
	}



	public void connectServer(String host, int port) throws IOException {
		client.connect(new InetSocketAddress(host, port));
		Log.i("test", "连接到服务器"+host+":"+port);
	}

	public void disconnectServer() throws IOException {
		client.disconnect();
	}

	public void requestBootStrap(){
		timeout=true;
		final BootStrapRequest request=new BootStrapRequest(1, LoginUtil.getUserId(netService.getApplicationContext()), "1".getBytes(),
				"android".getBytes(), msgId.addAndGet(1));
		bootStrapRequesThread = new Thread(new Runnable() {
			public void run() {
				while (timeout) {
					try {
						client.write(ByteBuffer.wrap(request.getMessage()));
						Log.i("test", "请求BootStrap");
						Thread.sleep(timeoutMillis);
					} catch (Exception e) {
						DeBugUtil.printException(netService,e);
						release();
						netService.restart();
					}
				}				
			}
		});
		bootStrapRequesThread.start();
	}
	
	public void login(int userId){
		timeout=true;
		long lastBroadCastId = InfoUtil.getLastBroadCastId(netService,userId);
		long lastUnicastId = InfoUtil.getLastUnicastId(netService,userId);
		final KeepLive login=new KeepLive(Global.NET_LOGIN_REQUEST, 1, msgId.addAndGet(1), userId, lastBroadCastId, lastUnicastId,0, null);
		loginThread = new Thread(new Runnable() {
			public void run() {
				try {
					while (timeout) {
						client.write(ByteBuffer.wrap(login.getMessage()));
						Log.i("test", "登录：" + login.toString());
						Thread.sleep(timeoutMillis);
					}
				} catch (Exception e) {
					DeBugUtil.printException(netService, e);
					release();
					netService.restart();
				}
			}
		});
		loginThread.start();
	}
	public void requestNotTimeOut() {
		timeout=false;
		Log.i("test", "请求未超时");
	}

	public void logout(int userId) throws IOException {
		Logout logout=new Logout(1, msgId.addAndGet(1), userId);
		client.write(ByteBuffer.wrap(logout.getMessage()));
		Log.i("test", logout.toString());
		//将lastUserid置0
		lastUserId=0;
		//停止心跳。
		stopKeepAlive();
		//将msgid清零
		msgId.set(0);
	}
	
	public void changeUser(int userId){
		try {
			//发送退出报文。
			logout(lastUserId);
			stopListen();
			//将lastUserId置为当前用户id
			lastUserId=userId;
			startListen();
			onReBootStrap();
		} catch (Exception e) {
			Log.i("test", e.getMessage(),e);
			DeBugUtil.printException(netService, e);
		}
	}

	public void setTimeOut(long timeoutMillis) {
		this.timeoutMillis=timeoutMillis;
	}
	
	public void startKeepAlive(final long timeMillis) {
		keeplive=true;
		keepAliveThread = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					while (keeplive) {
						HashSet<Long> options = new HashSet<Long>(userOptInfos);
						userOptInfos.clear();
						int size = options.size();
						int userId = LoginUtil.getUserId(netService);
						long lastBroadCastId = InfoUtil.getLastBroadCastId(netService,userId);
						long lastUnicastId = InfoUtil.getLastUnicastId(netService, userId);
						KeepLive keepLive = new KeepLive(Global.NET_KEEP_LIVE,1,msgId.addAndGet(1),
								userId,lastBroadCastId, lastUnicastId,size * 8, options);
						while (true) {
							if (client.isConnected()&&!client.isBlocking()) {
								client.write(ByteBuffer.wrap(keepLive.getMessage()));
								break;
							}
						}
						Log.i("test", keepLive.toString() + "  保活"+ keepAliveCount++ + "次");
						Thread.sleep(timeMillis);
					}
				} catch (Exception e) {
					Log.e("test", e.getMessage(),e);
					DeBugUtil.printException(netService, e);
					try {
						logout(LoginUtil.getUserId(netService));
					} catch (IOException e1) {
						release();
						netService.restart();
					}
				}
			}
		});
		keepAliveThread.start();
	}

	public void stopKeepAlive() {
		keeplive=false;
		keepAliveCount=1;
	}
	
	public void responseServer(InfoReceive infoReceive) throws IOException {
		InfoReceiveResponse response=new InfoReceiveResponse(infoReceive.protocolVersion, infoReceive.msgId);
		Log.i("test", response.toString());
		client.write(ByteBuffer.wrap(response.getMessage()));
	}
	
	public void startListen() {
		isListening=true;
		listeningThread = new Thread(new Runnable() {
			@Override
			public void run() {
				ByteBuffer inBuffer = ByteBuffer.allocate(1024);
				try {
					while (isListening) {
						if (selector.select() > 0) {
							onNewEvent(inBuffer);
						}
						Thread.sleep(1000);
					}
				} catch (Exception e) {
					DeBugUtil.printException(netService, e);
				}
			}
		});
		listeningThread.start();
	}
	
	public void stopListen() {
		isListening=false;
	}

	public void release(){
		try {
			timeout=false;
			stopListen();
			stopKeepAlive();
			broadcastBuffer.clear();
			unicastbuffer.clear();
			msgId.set(0);
			if (selector!=null) {
				selector.close();
			}
			if(client!=null){
				client.close();
			}
		} catch (IOException e) {
			Log.i("test", e.getMessage(),e);
			DeBugUtil.printException(netService, e);
		}
	}
	
	/**
	 * 有新事件被触发
	 * @param inBuffer
	 */
	private void onNewEvent(ByteBuffer inBuffer){
		try {
			for (Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); iterator.hasNext();) {
				SelectionKey sk = iterator.next();
				if (sk.isValid() && sk.isReadable()) {
					byte b = preTreatment(inBuffer, sk);
					dispatchEvent(inBuffer, b);
				}
			}
		} catch (IOException e) {
			DeBugUtil.printException(netService, e);
		}
	}

	/**
	 * 对事件做下简单的预处理，返回事件的类型
	 * @param inBuffer
	 * @param sk
	 * @return
	 * @throws IOException
	 */
	private byte preTreatment(ByteBuffer inBuffer, SelectionKey sk)
			throws IOException {
		DatagramChannel channel = (DatagramChannel) sk.channel();
		inBuffer.clear();
		channel.read(inBuffer);
		inBuffer.flip();
		byte b = inBuffer.get();
		return b;
	}
	/**
	 * 分派事件的处理
	 * @param inBuffer
	 * @param b
	 */
	private void dispatchEvent(ByteBuffer inBuffer, byte b){
		try {
			switch (b) {
			case Global.NET_BOOTSTRAP_ERROR:
				BootStrapError bootStrapError=new BootStrapError(inBuffer);
				onBootStrapError(bootStrapError);
				break;
			case Global.NET_BOOTSTRAP_RESPONSE:
				BootStrapResponse bootStrapResponse = new BootStrapResponse(inBuffer);
				onBootStrapSuccess(bootStrapResponse);
				break;
			// 登陆成功
			case Global.NET_LOGIN_RESPONSE:
				LoginResponse loginResponse = new LoginResponse(inBuffer);
				onLoginSuccess(loginResponse);
				break;
			// 登陆失败
			case Global.NET_LOGIN_ERROR:
				LoginError loginError = new LoginError(inBuffer);
				onLoginError(loginError);
				break;
			// 接收消息
			case Global.NET_HAS_NEW_INFO:
				InfoReceive infoReceive=new InfoReceive(inBuffer);
				onReceiveInfo(infoReceive);
				break;
			// 重新bootstrap
			case Global.NET_REBOOTSTRAP:
				Log.i("test", "再次重定向");
				onReBootStrap();
				break;
			}
		} catch (IOException e) {
			DeBugUtil.printException(netService, e);
		}
	}
	
	@Override
	public void onBootStrapError(BootStrapError bootStrapError)
			throws IOException {
		Toast.makeText(netService, bootStrapError.errorDesc, Toast.LENGTH_SHORT).show();
		netService.stopSelf();
	}

	@Override
	public void onBootStrapSuccess(BootStrapResponse bootStrapResponse)throws IOException {
		requestNotTimeOut();
		Log.i("test", bootStrapResponse.toString());
		disconnectServer();
		connectServer(bootStrapResponse.serverIp, bootStrapResponse.serverPort);
		login(LoginUtil.getUserId(netService.getApplicationContext()));
	}

	@Override
	public void onLoginError(LoginError loginError) throws IOException {
		Toast.makeText(netService, loginError.errorDesc, Toast.LENGTH_SHORT).show();
		netService.stopSelf();
	}
	
	
	@Override
	public void onLoginSuccess(LoginResponse loginResponse) {
		requestNotTimeOut();
		Log.i("test", loginResponse.toString());
		if (first) {
			startKeepAlive(loginResponse.keepaliveTime*1000);
			first=false;
		}
	}
	@Override
	public void onReceiveInfo(InfoReceive infoReceive) throws IOException {
		Log.i("test", infoReceive.toString());
		responseServer(infoReceive);
		int userId = LoginUtil.getUserId(netService);
		//是广播消息
		if (infoReceive.BorU==1) {
			InfoUtil.setLastBroadCastId(netService,userId,infoReceive.infoId);
			disposeBroadcastInfo(infoReceive);
		}else if (infoReceive.BorU==0) {
			InfoUtil.setLastUnicastId(netService,userId,infoReceive.infoId);
			disposeUnicastInfo(infoReceive);
		}
	}

	@Override
	public void onReBootStrap() throws IOException {
		requestNotTimeOut();
		first=true;
		stopKeepAlive();
		msgId.set(0);
		disconnectServer();
		connectServer(ConfigUtil.getBootStrapIp(netService), ConfigUtil.getBootStrapPort(netService));
		requestBootStrap();
	}
	/**
	 * 客户端是否启动
	 * @return
	 */
	public boolean isLuncher() {
		return isListening;
	}
	private boolean hasFinished=false;

	private void disposeBroadcastInfo(InfoReceive infoReceive) {
		//消息不分包
		if (infoReceive.udpPacketCount==1) {
			broadcastId=infoReceive.infoId;
			broadcastBuffer.clear();
			Information information = JsonUtil.parse(netService, infoReceive.data);
			if (information!=null) {
				Log.i("test", information.toString());
				saveInfo(information);
			}
			hasFinished=false;
			return;
		}
		//多包消息
		//当前处理的是新消息，那么将以前的缓存清空
		if (broadcastId!=infoReceive.infoId) {
			broadcastBuffer.clear();
			broadcastId=infoReceive.infoId;
			hasFinished=false;
		}
		if (!hasFinished) {
			//消息分包，将多个包都装入缓存
			if (infoReceive.udpPacketNo<infoReceive.udpPacketCount) {
				broadcastBuffer.put(infoReceive.udpPacketNo, infoReceive.data);
			}
			//消息已经收集齐全，解析消息，将消息推送出去
			if (infoReceive.udpPacketNo==infoReceive.udpPacketCount-1) {
				hasFinished=true;
				StringBuilder sb=new StringBuilder();
				int i=0;
				while (i<=infoReceive.udpPacketNo) {
					sb.append(broadcastBuffer.get(i));
					i++;
				}
				String data = sb.toString();
				Information information = JsonUtil.parse(netService.getApplicationContext(), data);
				if (information!=null) {
					Log.i("test", information.toString());
					saveInfo(information);
				}
			}
		}
	}
	private void disposeUnicastInfo(InfoReceive infoReceive) {
		//消息不分包
		if (infoReceive.udpPacketCount == 1) {
			unicastId = infoReceive.infoId;
			unicastbuffer.clear();
			Information information = JsonUtil.parse(netService,
					infoReceive.data);
			if (information != null) {
				Log.i("test", information.toString());
				saveInfo(information);
			}
			hasFinished=false;
			return;
		}
		//当前处理的是新消息，那么将以前的缓存清空
		if (unicastId!=infoReceive.infoId) {
			unicastbuffer.clear();
			unicastId=infoReceive.infoId;
			hasFinished=false;
		}
		if (!hasFinished) {
			//消息分包，将多个包都装入缓存
			if (infoReceive.udpPacketNo<infoReceive.udpPacketCount) {
				unicastbuffer.put(infoReceive.udpPacketNo, infoReceive.data);
			}
			//消息已经收集齐全，解析消息，将消息推送出去
			if (infoReceive.udpPacketNo==infoReceive.udpPacketCount-1) {
				hasFinished=true;
				StringBuilder sb=new StringBuilder();
				int i=0;
				while (i<=infoReceive.udpPacketNo) {
					sb.append(unicastbuffer.get(i));
					i++;
				}
				String data = sb.toString();
				Information information = JsonUtil.parse(netService.getApplicationContext(), data);
				if (information!=null) {
					Log.i("test", information.toString());
					saveInfo(information);
				}
			}
		}
	}
	private void saveInfo(final Information information) {
		saveInfoThread = new Thread(new Runnable() {
			@Override
			public void run() {
				int userId = LoginUtil.getUserId(netService);
				InfoDao dao=new InfoDao(netService);
				dao.open();
				dao.saveInfo(information, userId);
				dao.close();
				final int[] nums=WidgetService.NewsNums;
				switch (information.infoTypeId) {
				case Global.INFO_TYPE_SYS:
					nums[0]++;
					break;
				case Global.INFO_TYPE_APP:
					nums[1]++;
					break;
				case Global.INFO_TYPE_INTERACTIVE:
					nums[2]++;
					break;
				}
				netService.notifyHasNews();
			}
		});
		saveInfoThread.start();
	}
}
